/*
 * Copyright (C) 2025 ameise <ameise.wang@gmail.com> - All Rights Reserved
 *
 * This file is part of e3log.
 *
 * e3log is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * e3log is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with e3log. If not, see <https://www.gnu.org/licenses/>.
 */

package e3log

import (
	"strconv"
	"unicode/utf8"
)

const hex = "0123456789abcdef"

var noEscapeChar = [256]bool{}

func init() {
	for i := 0x20; i <= 0x7e; i++ {
		noEscapeChar[i] = i != '\\' && i != '"'
	}
}

type jsonEncoder struct{}

func (ts *jsonEncoder) begin(bs []byte) []byte {
	bs = append(bs, '{')
	return bs
}

func (ts *jsonEncoder) appendStr(bs []byte, key string, val string) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = ts.writeJsonString(bs, val)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendStrs(bs []byte, key string, vals []string) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = ts.writeJsonString(bs, val)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendErr(bs []byte, val error) []byte {
	bs = ts.writeJsonKey(bs, "error")
	if val == nil {
		bs = append(bs, "null"...)
	} else {
		bs = ts.writeJsonString(bs, val.Error())
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendErrs(bs []byte, vals []error) []byte {
	bs = append(ts.writeJsonKey(bs, "error"), '[')
	for _, val := range vals {
		if val == nil {
			bs = append(bs, "null,"...)
		} else {
			bs = ts.writeJsonString(bs, val.Error())
			bs = append(bs, ',')
		}
	}
	bs[len(bs)-1] = ']'
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt(bs []byte, key string, val int) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendInt(bs, int64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInts(bs []byte, key string, vals []int) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendInt(bs, int64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt8(bs []byte, key string, val int8) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendInt(bs, int64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt8s(bs []byte, key string, vals []int8) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendInt(bs, int64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt16(bs []byte, key string, val int16) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendInt(bs, int64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt16s(bs []byte, key string, vals []int16) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendInt(bs, int64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt32(bs []byte, key string, val int32) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendInt(bs, int64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt32s(bs []byte, key string, vals []int32) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendInt(bs, int64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt64(bs []byte, key string, val int64) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendInt(bs, val, 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendInt64s(bs []byte, key string, vals []int64) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendInt(bs, val, 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint(bs []byte, key string, val uint) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendUint(bs, uint64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUints(bs []byte, key string, vals []uint) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendUint(bs, uint64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint8(bs []byte, key string, val uint8) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendUint(bs, uint64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint8s(bs []byte, key string, vals []uint8) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendUint(bs, uint64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint16(bs []byte, key string, val uint16) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendUint(bs, uint64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint16s(bs []byte, key string, vals []uint16) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendUint(bs, uint64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint32(bs []byte, key string, val uint32) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendUint(bs, uint64(val), 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint32s(bs []byte, key string, vals []uint32) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendUint(bs, uint64(val), 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint64(bs []byte, key string, val uint64) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendUint(bs, val, 10)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendUint64s(bs []byte, key string, vals []uint64) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendUint(bs, val, 10)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendFloat32(bs []byte, key string, precision int, val float32) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendFloat(bs, float64(val), 'f', precision, 32)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendFloat32s(bs []byte, key string, precision int, vals []float32) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendFloat(bs, float64(val), 'f', precision, 32)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendFloat64(bs []byte, key string, precision int, val float64) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendFloat(bs, val, 'f', precision, 64)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendFloat64s(bs []byte, key string, precision int, vals []float64) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendFloat(bs, val, 'f', precision, 64)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendBool(bs []byte, key string, val bool) []byte {
	bs = ts.writeJsonKey(bs, key)
	bs = strconv.AppendBool(bs, val)
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendBools(bs []byte, key string, vals []bool) []byte {
	bs = append(ts.writeJsonKey(bs, key), '[')
	if len(vals) > 0 {
		for _, val := range vals {
			bs = strconv.AppendBool(bs, val)
			bs = append(bs, ',')
		}
		bs[len(bs)-1] = ']'
	} else {
		bs = append(bs, ']')
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendObject(bs []byte, val LogMarshaller) []byte {
	msg := val.E3LogMarshall()
	key, isJs := objCheckType(val, msg)
	bs = append(ts.writeJsonKey(bs, key))
	if isJs {
		bs = append(bs, msg...)
	} else {
		bs = ts.writeJsonString(bs, msg)
	}
	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) appendObjects(bs []byte, vals []LogMarshaller) []byte {
	msgStr := map[string][]string{}
	msgJs := map[string][]string{}
	for _, val := range vals {
		msg := val.E3LogMarshall()
		key, isJs := objCheckType(val, msg)
		if isJs {
			msgJs[key] = append(msgJs[key], msg)
		} else {
			msgStr[key] = append(msgStr[key], msg)
		}
	}

	if len(msgStr) > 0 {
		for k, vs := range msgStr {
			bs = append(ts.writeJsonKey(bs, k))
			if len(vs) > 1 {
				bs = append(bs, '[')
				for _, v := range vs {
					bs = ts.writeJsonString(bs, v)
					bs = append(bs, ',')
				}
				bs[len(bs)-1] = ']'
			} else {
				bs = ts.writeJsonString(bs, vs[0])
			}
		}
		bs = append(bs, ',')
	}

	for k, vs := range msgJs {
		bs = append(ts.writeJsonKey(bs, k))
		if len(vs) > 1 {
			bs = append(bs, '[')
			for _, v := range vs {
				bs = append(bs, v...)
				bs = append(bs, ',')
			}
			bs[len(bs)-1] = ']'
		} else {
			bs = append(bs, vs[0]...)
		}
	}

	bs = append(bs, ',')
	return bs
}

func (ts *jsonEncoder) writeJsonKey(bs []byte, s string) []byte {
	bs = append(bs, '"')
	bs = ts.escapeJsonStringBytes(bs, s)
	bs = append(bs, '"', ':')
	return bs
}

func (ts *jsonEncoder) writeJsonString(bs []byte, s string) []byte {
	bs = append(bs, '"')
	bs = ts.escapeJsonStringBytes(bs, s)
	bs = append(bs, '"')
	return bs
}

func (ts *jsonEncoder) escapeJsonStringBytes(dst []byte, s string) []byte {
	i := 0
	for i < len(s) {
		b := s[i]
		if b >= utf8.RuneSelf {
			_, size := utf8.DecodeRuneInString(s)
			i += size
		} else {
			if noEscapeChar[b] {
				i += 1
			} else {
				dst = append(dst, s[:i]...)
				switch b {
				case '"', '\\':
					dst = append(dst, '\\', b)
				case '\b':
					dst = append(dst, '\\', 'b')
				case '\f':
					dst = append(dst, '\\', 'f')
				case '\n':
					dst = append(dst, '\\', 'n')
				case '\r':
					dst = append(dst, '\\', 'r')
				case '\t':
					dst = append(dst, '\\', 't')
				default:
					dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
				}
				s = s[i+1:]
				i = 0
			}
		}
	}
	if len(s) > 0 {
		dst = append(dst, s[:]...)
	}
	return dst
}
